package set.test;

import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import set.src.CoarseList;
import set.src.FineList;
import set.src.LazyList;
import set.src.LockFreeList;
import set.src.OptimisticGlobalList;
import set.src.OptimisticList;
import set.src.Set;

public class Main {

    static final class Configuration {
        public String   algName;
        public int      test_no;
        public int      no_of_threads;

        public int      no_of_operations;
        public int      max_key;
        public int      insert_ops_percent;
        public int      delete_ops_percent;

        public int      initial_count;
        public int      timeToRun;

        public boolean read(String[] args) {
            try {
                //read configuration from input stream
                int curr_arg=0;

                test_no = Integer.parseInt(args[curr_arg++]);
                algName = args[curr_arg++];
                no_of_threads = Integer.parseInt(args[curr_arg++]);
                no_of_operations = Integer.parseInt(args[curr_arg++]);
                max_key = Integer.parseInt(args[curr_arg++]);
                insert_ops_percent = Integer.parseInt(args[curr_arg++]);
                delete_ops_percent = Integer.parseInt(args[curr_arg++]);
                initial_count = Integer.parseInt(args[curr_arg++]);
                timeToRun = Integer.parseInt(args[curr_arg++]);

                if (!algName.equals("LockFree") && !algName.equals("Lazy") && !algName.equals("Coarse") && !algName.equals("Fine") && !algName.equals("Optimistic") && !algName.equals("OptimisticGlobal")) {
                        System.out.println("Illgeal algorithmName. Should be one of the following:\n" +
                                        "Coarse\n" +
                                        "Fine\n" +
                                        "Lazy\n" +
                                        "Optimistic\n" +
                                        "OptimisticGlobal\n" +
                                        "LockFree\n\n\n");
                        return false;
                }

                return true;
            } catch (Exception e) {
                return false;
            }
        }
    }

    private static final class SetTestThread extends Thread {

        SetTestThread () {
        }

        @Override
        @SuppressWarnings("empty-statement")
        public void run() {
            final int threadNo = g_ThreadCounter.getAndIncrement();
            final int startIndex = 5* threadNo;
            if (threadNo == g_NumThreads - 1)
                g_StartTime = System.currentTimeMillis();
            while (g_NumThreads != g_ThreadCounter.get()) {;}
            eOpType opToSwitch;

            int i = startIndex;

            while (true) {

                switch (g_RandOpAry[i]) {
                case ADD:
                    g_Set.add(g_RandNumAry[i],threadNo);
                    g_adds[threadNo]++;
                    break;
                case REMOVE:
                    g_Set.remove(g_RandNumAry[i],threadNo);
                    g_deletes[threadNo]++;
                    break;
                case CONTAIN:
                    g_Set.contains(g_RandNumAry[i],threadNo);
                    g_containss[threadNo]++;
                    break;
                }
            	if(1== g_IsSEndThreads.get())
                    break;
            	++i;
            	if (i >= (g_Configuration.no_of_operations - 1))
                    i = 0;
            }
            final int endCounter = g_EndCounter.getAndIncrement();
            if (endCounter == g_NumThreads - 1)
                g_EndTime = System.currentTimeMillis();
        }
    }

    ////////////////////////////////////////////////////////////////////////////
    //GLOBALS
    ////////////////////////////////////////////////////////////////////////////
    static Set<Integer> 	g_Set;

    static Configuration    g_Configuration;
    static int              g_NumProcessors;
    static int              g_NumThreads;
    static int              g_InitialCount;

    static Random           g_Rand;
    static Integer[]        g_RandNumAry;
    static eOpType[]        g_RandOpAry;
    static Thread[]         g_Threads;

    static public int[] 	g_adds;
    static public int[]		g_deletes;
    static public int[]		g_containss;

    static AtomicInteger 	g_ThreadCounter;
    static AtomicInteger 	g_EndCounter;
    static AtomicInteger        g_IsSEndThreads;

    static volatile long        g_StartTime;
    static volatile long 	g_EndTime;

    ///////////////////////////////////////////////////
    static int                  g_numAddThreads;
    static int			g_numRemThreads;
    static int			g_numContainThreads;

    ////////////////////////////////////////////////////////////////////////////
    //MAIN
    ////////////////////////////////////////////////////////////////////////////
    public static void main(String[] args) throws InterruptedException {
        //read benchmark configuration
        g_Configuration =  new Configuration();
        if(!g_Configuration.read(args)) {
            System.out.println("USAGE: <testNum> <test type> <algorithem> <numThreads> <numActions> <maxKey> <insertOps> <deleteOps> <initialCount> <timeToRun>");
            System.exit(-1);
        }

        //initialize global variables
        g_Rand              = new Random();
        g_NumProcessors     = Runtime.getRuntime().availableProcessors();
        g_NumThreads        = g_Configuration.no_of_threads;
        g_InitialCount      = g_Configuration.initial_count;

        //run the benchmark
        RunBenchmark();
    }

    ////////////////////////////////////////////////////////////////////////////
    //HELPER FUNCTIONS
    ////////////////////////////////////////////////////////////////////////////
    private static void PrepareRandomNumbers() {
        g_RandNumAry = new Integer[g_Configuration.no_of_operations];
        g_RandOpAry  = new eOpType[g_Configuration.no_of_operations];

        int add_percent =  g_Configuration.insert_ops_percent;
        int remove_percent = add_percent + g_Configuration.delete_ops_percent;
        int op;

        for (int iRandNum = 0; iRandNum < g_RandNumAry.length; iRandNum++) {
            g_RandNumAry[iRandNum] = new Integer(g_Rand.nextInt(g_Configuration.max_key) + 1);
            op = g_Rand.nextInt(100) + 1;
            if (op <= add_percent)
                g_RandOpAry[iRandNum] = eOpType.ADD;
            else if (op <= remove_percent)
                g_RandOpAry[iRandNum] = eOpType.REMOVE;
            else
                g_RandOpAry[iRandNum] = eOpType.CONTAIN;
        }
    }

    private static void FillTable() {
        if(g_Set != null) {
            for (int iRandNum = 0; iRandNum < g_Configuration.initial_count; ++iRandNum) {
                g_Set.add(g_Rand.nextInt(g_Configuration.max_key) + 1, 0);
            }
        }
        if(g_Set != null)
            System.out.println("    Set Num elm:  " + g_Set.size(0));
    }

    private static final int RunBenchmark() throws InterruptedException {
        //print test information
        System.out.println("Concurrent Set Benchmark");
        System.out.println("---------------------------");
        System.out.println("    numOfThreads:      " + g_Configuration.no_of_threads);
        System.out.println("    Algorithem Name:   " + g_Configuration.algName);
        System.out.println("    NumProcessors:     " + g_NumProcessors);
        System.out.println("    testNo:            " + g_Configuration.test_no);
        System.out.println("    noOfActions:       " + g_Configuration.no_of_operations);
        System.out.println("    maxKey:            " + g_Configuration.max_key);
        System.out.println("    insertOps Perecentage: " + g_Configuration.insert_ops_percent);
        System.out.println("    deleteOps Perecentage: " + g_Configuration.delete_ops_percent);
        System.out.println("    initialCount:      " + g_Configuration.initial_count);
        System.out.println("    timeToRun:         " + g_Configuration.timeToRun);

        //create appropriate Set
        if(g_Configuration.algName.compareTo("Fine") == 0) {
        	g_Set = new FineList<Integer> (g_Configuration.no_of_threads);
        } else if (g_Configuration.algName.compareTo("Coarse") == 0) {
        	g_Set = new CoarseList<Integer> (g_Configuration.no_of_threads);
        } else if (g_Configuration.algName.compareTo("Lazy") == 0) {
        	g_Set = new LazyList<Integer> (g_Configuration.no_of_threads);
        } else if (g_Configuration.algName.compareTo("LockFree") == 0) {
        	g_Set = new LockFreeList<Integer> (g_Configuration.no_of_threads);
        } else if (g_Configuration.algName.compareTo("OptimisticGlobal") == 0) {
        	g_Set = new OptimisticGlobalList<Integer> (g_Configuration.no_of_threads);
        } else if (g_Configuration.algName.compareTo("Optimistic") == 0) {
        	g_Set = new OptimisticList<Integer> (g_Configuration.no_of_threads);
        } else {
        	System.out.println ("Unknown Set type: " + g_Configuration.algName);
        	return 1;
        }

        g_adds          = new int[g_Configuration.no_of_threads];
        g_deletes       = new int[g_Configuration.no_of_threads];
        g_containss     = new int[g_Configuration.no_of_threads];
        g_numAddThreads     = (int) Math.ceil(g_Configuration.no_of_threads * g_Configuration.insert_ops_percent / 100.0);
        g_numRemThreads     = (int) Math.ceil(g_Configuration.no_of_threads * g_Configuration.delete_ops_percent / 100.0);
        g_numContainThreads = g_Configuration.no_of_threads - g_numAddThreads - g_numRemThreads;

        g_ThreadCounter = new AtomicInteger(0);
        g_EndCounter    = new AtomicInteger(0);
        g_IsSEndThreads = new AtomicInteger(0);

        //prepare the random numbers
        System.out.println("");
        System.out.println("    START create random numbers.");
        PrepareRandomNumbers();
        System.out.println("    END   creating random numbers.");
        System.out.println("");

        //fill the Fine List
        System.out.println("    START fill table. ("+  g_Configuration.initial_count + ")");
        FillTable();
        System.out.println("    END   fill table.");
        System.out.println("");

        //create benchmark threads
        System.out.println("    START creating threads.");
        g_Threads =  new Thread[g_NumThreads];

        for(int iThread = 0; iThread < g_NumThreads; ++iThread) {
        	g_Threads[iThread] =  new SetTestThread();
        }
        

        System.out.println("    END   creating threads.");
        System.out.println("");

        //start the benchmark threads
        System.out.println("    START threads.");
        for(int iThread = 0; iThread < g_NumThreads; ++iThread) {
            g_Threads[iThread].start();
        }

        System.out.println("");
        System.out.println("    SINGMAL threads to start working.");
        System.out.println("sleeping for " +  g_Configuration.timeToRun + " seconds\n");
        Thread.sleep(g_Configuration.timeToRun * 1000);
        g_IsSEndThreads.set(1);

        //signal the threads to start benchmark
        System.out.println("    WAITING for threads to terminate.");

        //join the threads
        for(int iThread = 0; iThread < g_NumThreads; ++iThread) {
            g_Threads[iThread].join();
        }

        System.out.println("    ALL threads terminated.");
        System.out.println("");

        System.out.println("   Totla time: " + (g_EndTime- g_StartTime) + " miliseconds");

        //calculate threads results

        //print benchmark results
        if(null != g_Set ) {
            System.out.println("    Set Num elm: " +g_Set.size(0));
        }

        int totalAdds = 0, totalRemoves = 0, totalContains = 0;
        for (int i = 0 ; i< g_Configuration.no_of_threads; i++) {
            System.out.println("Thread no. " + i + " Adds: " + g_adds[i] + " ; Deletes: " + g_deletes[i] + " ; Contains: " + g_containss[i]);
            totalAdds += g_adds[i];
            totalRemoves += g_deletes[i];
            totalContains += g_containss[i];
        }
        int time = 10000;//(int)(g_EndTime- g_StartTime);
   
        System.out.println("\n\tTotal Adds: " + totalAdds + " ; Time: " + time + " ; Throughput: " + (time == 0 ? 0 : (double)totalAdds/time));

        System.out.println("\tTotal Removes: " + totalRemoves + " ; Time: " + time + " ; Throughput: " + (time == 0 ? 0 : (double)totalRemoves/time));

        System.out.println("\tTotal Contains: " + totalContains + " ; Time: " + time + " ; Throughput: " + (time == 0 ? 0 : (double)totalContains/time));
      
        int totalActions = totalAdds + totalRemoves + totalContains;
        System.out.println("\tTotal actions: " +  totalActions + " ; Time: " + time + " ; Throughput: " + (time == 0 ? 0 : (double)totalActions/time));
        

        //return benchmark results
        return 0;
    }

    enum eOpType {
    	ADD,
    	REMOVE,
    	CONTAIN,
    	NONE
    }

}//class Main